package com.lagou.homework;

import com.lagou.edu.factory.ProxyFactory;
import com.lagou.homework.annotation.AnnotationsScanner;
import com.lagou.homework.annotation.Autowired;
import com.lagou.homework.support.BeanDefinition;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author boting.guo
 * @date 2021/9/18 23:55
 */

public class DefaultListableBeanFactory {

	private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(256);

	private List<String> beanDefinitionNames = new ArrayList<>(256);

	private BeanDefinitionScanner scanner;


	private Map<String,Object> singletonObjects = new HashMap<>();  // 存储对象

	private AnnotatedBeanDefinitionReader reader;

	public DefaultListableBeanFactory() {
		this.scanner = new BeanDefinitionScanner(this);
		this.reader = new AnnotatedBeanDefinitionReader(this);
	}

	public DefaultListableBeanFactory(Class<?> componentClass) {
		this();
		try {
			register(componentClass);
			refresh();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void register(Class<?>... componentClass) throws Exception {
		this.reader.register(componentClass);
	}

	public void scan(String... basePackages) throws Exception {
		this.scanner.scan(basePackages);
	}

	private void refresh() throws Exception {
		scanComponents(this);

		finishBeanFactoryInitialization();
	}

	private void scanComponents(DefaultListableBeanFactory defaultListableBeanFactory) throws Exception {
		List<String> names = new ArrayList<>(beanDefinitionNames);
		for (String bdn : names) {
			this.scanner.scan(this.beanDefinitionMap.get(bdn));
		}
	}

	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition == null) {
			this.beanDefinitionMap.put(beanName, beanDefinition);
			this.beanDefinitionNames.add(beanName);
		} else {
			// 直接覆盖原有的BeanDefinition
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
	}

	public List<String> getBeanDefinitionNames() {
		return this.beanDefinitionNames;
	}

	private void finishBeanFactoryInitialization() throws Exception {
		for (String bdn : beanDefinitionNames) {
			getBean(bdn);
		}
	}

	public Object getBean(String beanName) throws Exception {

		Object beanInstance = this.singletonObjects.get(beanName);
		if (beanInstance != null) {
			return beanInstance;

		} else {
			BeanDefinition bd = this.beanDefinitionMap.get(beanName);
			beanInstance = createBean(beanName, bd);
			singletonObjects.put(beanName, beanInstance);
			return beanInstance;
		}
	}

	public Object getBean(Class<?> requiredType) {

		for (String beanName : beanDefinitionNames) {
			Object bean = singletonObjects.get(beanName);
			if (bean != null && requiredType.isAssignableFrom(bean.getClass())) {
				return bean;
			}
		}

		for (String beanName : beanDefinitionNames) {
			BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
			Class<?> beanClass = (Class<?>) beanDefinition.getBeanClass();
			if (requiredType.isAssignableFrom(beanClass)) {
				try {
					return getBean(beanName);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		throw new RuntimeException("No such bean definition whose type is " + requiredType.getName());
	}

	private Object createBean(String beanName, BeanDefinition bd) throws Exception {

		// 按照spring的做法，这里是要分三步的，但是这里出于练习的目的，省略某些步骤
		// 不考虑循环依赖

		Class<?> aClass = (Class<?>) bd.getBeanClass();

		// 通过反射创建对象
		Constructor<?> constructor = aClass.getDeclaredConstructor();
		Object instance = constructor.newInstance();

		// 填充属性
		populateBean(beanName, bd, instance);
		
		// 初始化bean
		instance = initializeBean(beanName, instance, bd);
		this.singletonObjects.put(beanName, instance);
		return instance;
	}

	private Object initializeBean(String beanName, Object instance, BeanDefinition bd) throws Exception {
		// 这里就直接对@Transactional进行后置处理

		Class<?> beanClass = (Class<?>) bd.getBeanClass();
		Annotation[] declaredAnnotations = AnnotationsScanner.getDeclaredAnnotations(beanClass);
		boolean needProxy = false;
		for (Annotation annotation: declaredAnnotations) {
			if (annotation.annotationType().getName().equals("com.lagou.homework.annotation.Transactional")) {
				needProxy = true;
				break;
			}
		}

		Method[] declaredMethods = beanClass.getDeclaredMethods();
		for (Method method : declaredMethods) {
			Annotation[] annos = method.getDeclaredAnnotations();
			for (Annotation annotation: annos) {
				String annotationTypeName = annotation.annotationType().getName();
				if (annotationTypeName.equals("com.lagou.homework.annotation.Transactional")) {
					needProxy = true;
					break;
				}
			}
			if (needProxy) {
				break;
			}
		}
		if (needProxy) {
			ProxyFactory proxyFactory = (ProxyFactory) getBean("com.lagou.edu.factory.proxyfactory");
			if (beanClass.getInterfaces().length == 0) {
				instance = proxyFactory.getCglibProxy(instance);
			} else {
				instance = proxyFactory.getJdkProxy(instance);
			}
		}
		return instance;
	}

	private void populateBean(String beanName, BeanDefinition bd, Object instance) throws Exception {

		Class<?> beanClass = (Class<?>) bd.getBeanClass();
		Field[] declaredFields = beanClass.getDeclaredFields();
		for (Field field : declaredFields) {
			if (field.isAnnotationPresent(Autowired.class)) {
				Class<?> requiredType = field.getType();
				Object bean = getBean(requiredType);
				field.setAccessible(true);
				field.set(instance, bean);
			}
		}

	}
}
